﻿using System;
using System.IO;
using System.Collections.Generic;

using PickGold.Data.Hsts.General.Compression;

namespace PickGold.Data.Hsts.General.Persist
{
	public class Int64IndexerPersist : IIndexerPersist<long>
	{
		public const byte VERSION = 40;

		private long[] factors;

		/// <summary>
		/// This contructor gets the factors in ascending order
		/// </summary>
		public Int64IndexerPersist(long[] factors)
		{
			this.factors = factors;
		}

		public Int64IndexerPersist()
			: this(new long[0])
		{
		}

		public void Store(BinaryWriter writer, Func<int, object, long> values, int count, object state)
		{
			writer.Write(VERSION);

			var array = new long[count];

			var index = factors.Length - 1;
			for (var i = 0; i < count; i++)
			{
				var value = values(i, state);
				array[i] = value;

				while (index >= 0)
				{
					if (value % factors[index] == 0)
						break;
					else
						index--;
				}
			}

			var factor = index >= 0 ? factors[index] : 1L;

			var helper = new DeltaCompression.Helper();
			for (var i = 0; i < count; i++)
			{
				array[i] /= factor;
				helper.Add(array[i]);
			}

			CountCompression.Serialize(writer, checked((ulong)factor));
			DeltaCompression.Compress(writer, array, 0, count, helper);
		}

		public void Load(BinaryReader reader, Action<int, object, long> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid Int64IndexerPersist version.");

			var factor = (long)CountCompression.Deserialize(reader);

			DeltaCompression.Decompress(reader, (idx, o, val) => values(idx, o, factor * val), count, state);
		}
	}

	public class UInt64IndexerPersist : IIndexerPersist<ulong>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, ulong> values, int count, object state)
		{
			writer.Write(VERSION);
			persist.Store(writer, (i, o) => (long)values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, ulong> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid UInt64IndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (ulong)v), count, state);
		}
	}

	public class Int32IndexerPersist : IIndexerPersist<int>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, int> values, int count, object state)
		{
			writer.Write(VERSION);

			persist.Store(writer, (i, o) => values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, int> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid Int32IndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (int)v), count, state);
		}
	}

	public class UInt32IndexerPersist : IIndexerPersist<uint>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, uint> values, int count, object state)
		{
			writer.Write(VERSION);

			persist.Store(writer, (i, o) => values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, uint> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid UInt32IndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (uint)v), count, state);
		}
	}

	public class Int16IndexerPersist : IIndexerPersist<short>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, short> values, int count, object state)
		{
			writer.Write(VERSION);

			persist.Store(writer, (i, o) => values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, short> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid Int16IndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (short)v), count, state);
		}
	}

	public class UInt16IndexerPersist : IIndexerPersist<ushort>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, ushort> values, int count, object state)
		{
			writer.Write(VERSION);

			persist.Store(writer, (i, o) => values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, ushort> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid UInt16IndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (ushort)v), count, state);
		}
	}

	public class ByteIndexerPersist : IIndexerPersist<byte>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, byte> values, int count, object state)
		{
			writer.Write(VERSION);

			persist.Store(writer, (i, o) => values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, byte> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid ByteIndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (byte)v), count, state);
		}
	}

	public class SByteIndexerPersist : IIndexerPersist<sbyte>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, sbyte> values, int count, object state)
		{
			writer.Write(VERSION);

			persist.Store(writer, (i, o) => values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, sbyte> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid SByteIndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (sbyte)v), count, state);
		}
	}

	public class CharIndexerPersist : IIndexerPersist<char>
	{
		public const byte VERSION = 40;

		private readonly Int64IndexerPersist persist = new Int64IndexerPersist();

		public void Store(BinaryWriter writer, Func<int, object, char> values, int count, object state)
		{
			writer.Write(VERSION);

			persist.Store(writer, (i, o) => values(i, o), count, state);
		}

		public void Load(BinaryReader reader, Action<int, object, char> values, int count, object state)
		{
			if (reader.ReadByte() != VERSION)
				throw new Exception("Invalid CharIndexerPersist version.");

			persist.Load(reader, (i, o, v) => values(i, o, (char)v), count, state);
		}
	}
}
